package modviewcontrol;

import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;


public class NodeIO {
	private enum state{START,TAG,ITEM,ATT,ASSIGN,SKIP,ATT_TRANS,START_TRANS};
	private enum TokenType{NONE,ELEMENT,ELEMENT_CLOSE,ITEM};
	private static state currState = state.START;
	private static Token globalToken;
	private static String globalString;
	//private static Pattern schemaDeclaration = Pattern.compile("<(\\w*) title=\"([\\w\\s\\p{alnum}]*)\">");
	//private static Pattern ending      = Pattern.compile("</.*>");
	public static void save(Node root, String fileName) throws IOException{
		if(fileName != null){
			BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
//			System.out.println("Schema:");
			saveSchema(root,writer,0);
//			System.out.println("Tree:");
			writer.write('\n');
			saveTree(root,writer,0);
			writer.flush();
			writer.close();
		}
	}

	public static Node load(String fileName) throws IOException{
		assert(fileName!=null);
		currState = state.START;

		InputStreamReader reader = new InputStreamReader(new FileInputStream(fileName));

		globalToken = tokenize(reader);
		Token tmp = globalToken;
		while(tmp!=null){
			//System.out.println(tmp.toString());
			tmp = tmp.next;
		}
		Node root = loadSchema();
		loadActualChildren(root);
		
		output(root,0);
		return root;
	}

	public static Node load(Reader reader) throws IOException{
		assert(reader!=null);
		currState = state.START;

		globalToken = tokenize(reader);
		Token tmp = globalToken;
		while(tmp!=null){
			//System.out.println(tmp.toString());
			tmp = tmp.next;
		}
		Node root = loadSchema();
		loadActualChildren(root);
		
		output(root,0);
		return root;
	}
	
	public static void output(Node node, int i){
		//System.out.println(node);
		for(Node child : node.getActualChildren())
			output(child,++i);
	}

	private static Node loadSchema(){
		Node curr = tokenToNode(globalToken);
		if(curr==null)
			return null;
		globalToken = globalToken.next;

		while(globalToken.type==TokenType.ELEMENT){
			Node tmp = loadSchema();
			if(tmp!=null)
				curr.addPossChild(tmp);
		}

		globalToken = globalToken.next;
		return curr;
	}

	private static void loadActualChildren(Node curr){
		//System.out.println("--==-- "+globalToken);
		//System.out.println("parent:"+curr);
		if(tokenToNode(globalToken).getNodeType()==Node.Type.SCHEME){
			globalToken = globalToken.next;

			while(globalToken.type == TokenType.ELEMENT)
				loadActualChildren(curr);

			globalToken = globalToken.next;
		}else if(tokenToNode(globalToken).getNodeType()==Node.Type.PATTERN){
			Node tmp = curr.addPatternChild();
			//System.out.println("PATTERN ADDED: "+curr);
			//System.out.println("contains: "+curr.getActualChildren());
			//System.out.println("possChld: "+tmp.getPossChildren());
			if(tokenToNode(globalToken).getRegex()!=null){
				tmp.setRegex(tokenToNode(globalToken).getRegex());
			}
			if(tokenToNode(globalToken).getNodeColor()!=null){
				tmp.setNodeColor((tokenToNode(globalToken).getNodeColor()));
			}
			globalToken = globalToken.next;

			while(globalToken.type == TokenType.ELEMENT)
				loadActualChildren(tmp);

			globalToken = globalToken.next;
		}else if(tokenToNode(globalToken).getNodeType()==Node.Type.SINGLE
				||tokenToNode(globalToken).getNodeType()==Node.Type.MULTI){
			Node tmp = curr.addActualChild(tokenToNode(globalToken).getParentIdx());
			
			globalToken = globalToken.next;

			while(globalToken.type == TokenType.ELEMENT)
				loadActualChildren(tmp);

			globalToken = globalToken.next;
		}else if(tokenToNode(globalToken).getNodeType()==Node.Type.IMPORT){
			Node tmp = curr.addImportChild();
			if(tokenToNode(globalToken).getTitle()!=null)
				tmp.setTitle(tokenToNode(globalToken).getTitle());
			globalToken = globalToken.next;

			while(globalToken.type == TokenType.ELEMENT)
				loadActualChildren(tmp);

			globalToken = globalToken.next;
		}
	}

	private static void saveSchema(Node node, BufferedWriter writer, int tabs) throws IOException{
		assert(node!=null && writer!=null);
//		System.out.println(node.xmlSchemaHead());
		writeTabs(writer, tabs);
		writer.write(node.xmlSchemaHead());
		if(node.getPossChildren()!=null)
			for(Node possChild : node.getPossChildren()){
				saveSchema(possChild, writer, tabs+1);
			}
//		System.out.println(node.xmlTail());
		writeTabs(writer, tabs);
		writer.write(node.xmlTail());
	}

	private static void saveTree(Node node, BufferedWriter writer, int tabs)throws IOException{
		assert(node!=null&&writer!=null);
//		System.out.println(node.xmlHead());
		writeTabs(writer, tabs);
		writer.write(node.xmlHead());
		if(node.getActualChildren()!=null)
			for(Node actualChild : node.getActualChildren())
				saveTree(actualChild, writer, tabs+1);
//		System.out.println(node.xmlTail());
		writeTabs(writer, tabs);
		writer.write(node.xmlTail());
	}

	private static void writeTabs(BufferedWriter writer, int tabs) throws IOException {
		for(int i=0;i<tabs;i++){
			writer.write('\t');
		}
	}
	public static String makeTabs(int tabs){
		String result = "";
		for(int i=0;i<tabs;i++){
			result += '\t';
		}
		return result;
	}

	private static Node tokenToNode(Token token){
		Node node = new Node();

		for(Node.Type tmp : Node.Type.values()){
			if(token.name.equals(tmp.name()))
				node.setNodeType(tmp);
		}

		for(int i=0; i<token.attributes.size(); i++){
//			//System.out.println(token.attributes.get(i)+" --> "+token.attributeAssigns.get(i));
			if(token.attributes.get(i).equals("regex"))
				node.setRegex(token.attributeAssigns.get(i));
			if(token.attributes.get(i).equals("title")){
					node.setTitle(token.attributeAssigns.get(i));
			}
			if(token.attributes.get(i).equals("parentIdx")){
				node.setParentIdx(Integer.parseInt(token.attributeAssigns.get(i)));
			}
			if(token.attributes.get(i).equals("color")){
				node.setNodeColor(token.attributeAssigns.get(i));
			}
		}
		return node;
	}

	private static Token tokenize(Reader in)throws IOException{
		assert(in.ready());
		int ch = 0;
		NodeIO x = new NodeIO();
		Token rootToken;
		Token currToken = rootToken = x.new Token();
		globalString = new String();
		while((ch = in.read())!=-1){
//			System.out.print(currState);
	//		System.out.println((char)ch);
			currToken = tagState((char)ch, currToken);
			currToken = assignState((char)ch, currToken);
			currToken = attState((char)ch, currToken);
			currToken = startState((char)ch, currToken);
			currToken = itemState((char)ch, currToken);
		}
		return rootToken;
	}

	private static Token startState(char ch, Token currToken){
		if(currState==state.START){
			if(ch=='<')
				currState = state.TAG;
			else if(!(ch==' '||ch=='\n'||ch=='\r'||ch=='\t'))
				currState = state.ITEM;
		}else if(currState==state.START_TRANS)
			currState = state.START;
		return currToken;
	}

	private static Token itemState(char ch, Token currToken){
		if(currState==state.ITEM){
			currToken.type = TokenType.ITEM;
			if(ch=='<'){
				currState = state.TAG;
				NodeIO x = new NodeIO();
				currToken.next = x.new Token();
				currToken = currToken.next;
			}else{
//				System.out.println("ITEM STATE HAS CHANGED NAME! "+ch);
				currToken.name+=ch;
			}
		}
		return currToken;
	}

	private static Token tagState(char ch, Token currToken){
		//System.out.println(currState.toString());
		if(currState==state.TAG){
			if(currToken.type==TokenType.NONE)
				currToken.type = (ch=='/'?TokenType.ELEMENT_CLOSE:TokenType.ELEMENT);
			if(Character.isJavaIdentifierPart(ch)||ch=='/'||ch=='<'){
				if(ch!='<'){
//					System.out.println("NAME CHANGED! "+ch);
					currToken.name += ch;
				}
			}
			else{
//				System.out.println("Not a JavaIdentifierPart: "+ch);
				currState = state.ATT;
			}
		}

		return currToken;
	}

	private static Token assignState(char ch, Token currToken){
		if(currState==state.ASSIGN){
			if(ch=='\"'){
				currState = state.ATT_TRANS;
				currToken.attributeAssigns.add(globalString);
				globalString = new String();
			}else
				globalString+=ch;
			if(ch=='\\')
				currState = state.SKIP;

		}else if(currState == state.SKIP){
			globalString+=ch;
			currState = state.ASSIGN;
		}

		return currToken;
	}

	private static Token attState(char ch, Token currToken){
		if(currState==state.ATT){
			if(ch=='\"'){
				currToken.attributes.add(globalString);
				globalString = new String();
				currState = state.ASSIGN;
			}
			else if(ch!='='&&ch!='>'&&ch!=' ')
				globalString+=ch;


			if(ch=='>'){
				currState = state.START_TRANS;
				NodeIO x = new NodeIO();
				currToken.next = x.new Token();
				currToken = currToken.next;
			}
		}else if(currState == state.ATT_TRANS){
			currState = state.ATT;
		}
		return currToken;
	}

	private class Token{
		public String name;
		public List<String> attributes;
		public List<String> attributeAssigns;
		public Token next;
		public TokenType type;

		public Token(){
			name = new String();
			attributes = new ArrayList<String>();
			attributeAssigns = new ArrayList<String>();
			next = null;
			type = TokenType.NONE;
		}

		@Override
		public String toString(){
			return "name: "+name+'-' + attributes.toString()+
			attributeAssigns.toString()+type.toString();
		}
	}
}
